/**
 *Copyright 2013 by dragon.
 *
 *File name: DownloadUtils.java
 *Author:      dragon
 *Email:       fufulove2012@gmail.com
 *Blog:        http://blog.csdn.net/xidomlove
 *Version:     1.0.0
 *Date:        2013-10-4 下午4:25:17
 *Description: 程序入口，处理用户参数并准备下载环境
 */
package com.dragon.jaxel;

import gnu.getopt.Getopt;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.UnsupportedEncodingException;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Scanner;

import com.dragon.jaxel.bittorrent.BitTorrentDownloader;
import com.dragon.jaxel.bittorrent.bencode.Bencode;
import com.dragon.jaxel.bittorrent.bencode.BencodeDictionary;
import com.dragon.jaxel.bittorrent.bencode.BencodeList;
import com.dragon.jaxel.bittorrent.bencode.BencodeString;
import com.dragon.jaxel.bittorrent.bencode.BencodeValue;
import com.dragon.jaxel.xtp.FtpDownloader;
import com.dragon.jaxel.xtp.HttpDownloader;
import com.dragon.log.Logger;

/**
 * @author dragon8
 * 
 */
public class DownloadUtils {

	static boolean isContinue = false;
	static int connCount = -1;

	static boolean success = false;
	/**
	 * 是否是交互模式
	 */
	static boolean interactive = false;

	private static void printUage() {
		System.out.println();
		System.out
				.println("Usage: java -jar JAxel.jar [option] [arg] [file|url]");
		System.out.println();
		System.out.println("Options: ");
		System.out
				.println("\t-n: Number of connections(default: depends on file size).");
		System.out.println("\t-o: Output file name(default: index.html).");
		System.out.println("\t-d: Save to specific directory.");
		System.out.println("\t-u: Set user name if neccessary.");
		System.out.println("\t-p: Set pass word if neccessary.");
		System.out.println("\t-l: enable log to specific file.");
		System.out.println();
		System.out.println("Examples:");
		System.out
				.println("Download a file: java -jar JAxel.jar -n4 http://google.com/file.rar -o/home/downloads/a.rar");
		System.out
				.println("Continue to download a file not compelete: java -jar JAxel.jar /home/downloads/a.rar.jaxel");

		System.out.println();
		System.out.println("Contact me: fufulove2012@gmail.com");
		System.exit(-1);
	}

	private static Downloader getDownloader(String[] args)
			throws UnsupportedEncodingException, FileNotFoundException {
		if (args.length < 1) {
			printUage();
		}

		Getopt getopt = new Getopt("JAxel", args, "n:o:u:p:l:id:");
		String savePathString = null;
		String userString = null;
		String passString = null;
		int ch;
		while ((ch = getopt.getopt()) != -1) {
			switch (ch) {
			case 'n':
				connCount = Integer.valueOf(getopt.getOptarg());
				break;
			case 'o':
				savePathString = getopt.getOptarg();
				break;
			case 'u':
				userString = getopt.getOptarg();
				break;
			case 'p':
				passString = getopt.getOptarg();
				break;
			case 'l':
				Logger.setDefaultLogger(new Logger(getopt.getOptarg(), true));
				break;
			case 'i':
				interactive = true;
				break;
			case 'd':
				savePathString = getopt.getOptarg();
				break;
			default:
				printUage();
				break;
			}
		}
		if (getopt.getOptind() >= args.length) {
			printUage();
		}
		String url = args[getopt.getOptind()];

		if (url.startsWith("http:")) {
			// http 协议
			return new HttpDownloader(url, savePathString);
		} else if (url.startsWith("ftp:")) {
			if (savePathString == null) {
				savePathString = new File(
						url.substring(url.lastIndexOf("/") + 1))
						.getAbsolutePath();
			} else if (new File(savePathString).isDirectory()) {
				savePathString = new File(savePathString + "/"
						+ url.substring(url.lastIndexOf("/") + 1))
						.getAbsolutePath();
			}
			// http 协议
			FtpDownloader downloader = new FtpDownloader(url, savePathString);
			if (userString != null) {
				downloader.setUserString(userString);
			}
			if (passString != null) {
				downloader.setPasswordString(passString);
			}
			return downloader;
		} else if (url.endsWith(".jaxel")) {
			try {
				isContinue = true;
				ObjectInputStream inputStream = new ObjectInputStream(
						new FileInputStream(url));
				Class<?> cls = Class.forName(inputStream.readUTF());
				Downloader downloader = (Downloader) cls.getConstructor()
						.newInstance();
				downloader.loadState(inputStream);
				inputStream.close();
				return downloader;
			} catch (Exception e) {
				// 读取状态文件失败
				System.out.print("Error: invalid downloading state file");
				System.exit(-1);
			}

		} else if (url.endsWith(".torrent")) {
			try {
				BencodeDictionary metaData = Bencode.decodeFile(url)
						.getAsDictionary();
				BencodeDictionary dictionary = metaData.get("info")
						.getAsDictionary();
				BencodeValue files = dictionary.get("files");
				if (files == null) {
					// 单个文件
					BencodeString pathString = (BencodeString) dictionary
							.get("name.utf-8");
					if (passString == null) {
						pathString = dictionary.get("name")
								.getAsBencodeString();
					}
					if (savePathString == null) {
						savePathString = pathString.getData();
					}
					return new BitTorrentDownloader(metaData, -1, 0,
							savePathString);
				} else {
					// 多个文件
					int i = 0;
					System.out
							.println("The bit torrent contains more than one file:");
					for (BencodeValue file : files.getAsBencodeList().getList()) {
						BencodeDictionary filemMap = file.getAsDictionary();
						BencodeList paths = (BencodeList) filemMap
								.get("path.utf-8");
						if (paths == null) {
							paths = filemMap.get("path").getAsBencodeList();
						}
						System.out.print(i++ + ": .");
						for (BencodeValue object : paths.getList()) {
							System.out.print("/"
									+ object.getAsBencodeString().getData());
						}
						System.out.println();
					}
					System.out.print("Please select a file to download:");
					Scanner scanner = new Scanner(System.in);
					i = scanner.nextInt();
					scanner.close();
					if (i > files.getAsBencodeList().size()) {
						System.out.println("Error: Invalid index");
						System.exit(-1);
					}
					BencodeList paths = (BencodeList) files.getAsBencodeList()
							.get(i).getAsDictionary().get("path.utf-8");
					if (paths == null) {
						paths = files.getAsBencodeList().get(i)
								.getAsDictionary().get("path")
								.getAsBencodeList();
					}
					if (savePathString == null) {
						savePathString = ".";
						for (BencodeValue object : paths.getList()) {
							savePathString += "/"
									+ object.getAsBencodeString().getData();
						}
					}

					// 计算文件偏移
					long fileOffset = 0;
					int idx = 0;
					for (BencodeValue file : files.getAsBencodeList().getList()) {
						if (idx++ == i) {
							break;
						}
						fileOffset += file.getAsDictionary().get("length")
								.getAsBencodeNumber().getData();
					}
					return new BitTorrentDownloader(metaData, i, fileOffset,
							savePathString);
				}

			} catch (IOException e) {
				Logger.getDefaultLogger().info(
						"Bencoding decode Exception:" + e.getMessage());
				System.out.println("Error: invalid bit torrent file");
				System.exit(-1);
			}
		}
		return null;
	}

	public static String humanReadableByteCount(long bytes, boolean si) {
		int unit = si ? 1000 : 1024;
		if (bytes < unit)
			return bytes + " B";
		int exp = (int) (Math.log(bytes) / Math.log(unit));
		String pre = (si ? "KMGTPE" : "KMGTPE").charAt(exp - 1)
				+ (si ? "" : "i");
		return String.format("%.2f %sB", bytes / Math.pow(unit, exp), pre);
	}

	public static void main(String[] args) throws InterruptedException,
			IOException {
		Logger.setDefaultLogger(new Logger(System.out));
		Logger.getDefaultLogger().disableLog();
		final Downloader downloader = getDownloader(args);
		try {

			if (isContinue) {
				success = downloader.resume();
			} else {
				if (connCount > 0) {
					success = downloader.start(connCount);
				} else {
					success = downloader.start();
				}

			}
		} catch (IOException e) {
			System.out
					.println("Connect failed, please check your network state");
			System.exit(-1);
		}
		if (!success) {
			System.out.println("Fail to initial downloading file.");
			System.exit(-1);
		}

		if (interactive) {
			DataInputStream inputStream = new DataInputStream(System.in);
			DataOutputStream outputStream = new DataOutputStream(System.out);
			int choice;
			while (true) {
				// "握手"确认
				choice = inputStream.readInt();
				Logger.getDefaultLogger().debug("recv random: " + choice);
				outputStream.writeInt(choice);
				outputStream.flush();
				choice = inputStream.readInt();
				Logger.getDefaultLogger().debug("recv choice: " + choice);
				switch (choice) {
				case 0:
					// 是否在下载
					if (downloader.isDownLoading()) {
						outputStream.writeInt(1);
					} else {
						outputStream.writeInt(0);
					}
					break;
				case 1:
					// 获取保存的文件名
					byte[] name = downloader.getSavePathString().getBytes(
							"utf8");
					outputStream.writeInt(name.length);
					outputStream.write(name);
					break;
				case 2:
					// 获取文件长度
					outputStream.writeLong(downloader.getSize());
					break;
				case 3:
					// 获取进度
					outputStream.writeLong(downloader.getProgress());
					break;
				case 4:
					// 暂停下载
					downloader.pause();
					break;
				case 5:
					// 继续下载
					downloader.resume();
					break;
				case 6:
					// 停止下载并退出下载进程
					System.exit(0);
					break;
				default:
					break;
				}
				outputStream.flush();
			}
		}

		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

			@Override
			public void run() {
				if (!success) {
					Logger.getDefaultLogger().close();
					return;
				}
				if (!downloader.isCompleted()) {
					// not compelete
					try {
						downloader.stop();
						System.out.println();
						System.out.println();
						System.out
								.println("Downloading exit but not compelete, please restart application to resume downloading");
						System.out.println();
						if (downloader.isSupportRange()) {
							System.out.println("Saving download state...");
							downloader.saveState();
							System.out.println("The state file has saved in "
									+ new File(downloader.getSavePathString())
											.getAbsolutePath() + ".jaxel");
						}
					} catch (IOException e) {
						e.printStackTrace();
						System.out.println("Save error!");
					}
				} else {
					downloader.deleteSateFile();
					System.out.println();
					System.out.println();
					System.out.println("Download success!");
					System.out.println("The file has saved in "
							+ new File(downloader.getSavePathString())
									.getAbsolutePath());
				}
				Logger.getDefaultLogger().close();
			}
		}));
		long size = downloader.getSize();
		double dsize = (double) size;
		System.out.println("File size: "
				+ NumberFormat.getNumberInstance(Locale.US).format(size)
				+ " Bytes" + " (" + humanReadableByteCount(size, false) + ")");
		System.out.println();
		long bytesRead = downloader.getProgress();
		long startTime = System.currentTimeMillis();

		if (!downloader.isSupportRange()) {
			System.out
					.println("The server dose not support range downloading, now use a single connection to download.");
		}
		// 每5000毫秒更新一次，用于计算下载速度
		long bytesReadStage = bytesRead;
		long startTimeStage = startTime;
		Thread.sleep(600);
		while (true) {
			Thread.sleep(1000);
			bytesRead = downloader.getProgress();
			int percent = (int) (bytesRead / dsize * 100);
			long millis = System.currentTimeMillis() - startTime;
			long seconds = millis / 1000;
			long second = (seconds) % 60;
			long minute = (millis / (1000 * 60)) % 60;
			long hour = (millis / (1000 * 60 * 60)) % 24;

			long millisPart = System.currentTimeMillis() - startTimeStage;
			long bytesPerSecond = (bytesRead - bytesReadStage)
					/ (millisPart / 1000);
			if (millisPart > 5000) {
				startTimeStage = System.currentTimeMillis();
				bytesReadStage = bytesRead;
			}
			long leftseconds = Integer.MAX_VALUE;
			if (bytesPerSecond != 0) {
				leftseconds = (size - bytesRead) / bytesPerSecond;
			}
			long leftsecond = (leftseconds) % 60;
			long leftminute = (leftseconds / (60)) % 60;
			long lefthour = (leftseconds / (60 * 60)) % 24;
			if (!downloader.isDownLoading()) {
				break;
			}
			System.out
					.printf("\r[%d%%] [%s] %s/s Time: %02d:%02d:%02d, Left: %02d:%02d:%02d        \b\b\b\b\b\b\b\b",
							percent, humanReadableByteCount(bytesRead, false),
							humanReadableByteCount(bytesPerSecond, false),
							hour, minute, second, lefthour, leftminute,
							leftsecond);
		}
	}
}
